home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacWorld 1999 January - Disc 2
/
Macworld (1999-01) (Disk 2).dmg
/
Serious Demos
/
Symbolic Composer 4.2
/
Environment
/
Projects
/
Tutorial Material
/
Basic Tutorial
/
Lisp for Musicians
next >
Wrap
Lisp/Scheme
|
1998-10-26
|
11KB
|
435 lines
A Lisp Tutorial For Musicians
You may well have a book on Lisp. But, its examples are unlikely
to have anything to do with music. This tutorial shows how you might
begin to make sense of the primitives of Lisp in musical situations.
These primitives are the building blocks of SCOM's functions. They
will be invaluable as you begin to design and customize your own
functions.
This is an interactive tutorial. Evaluate every expression you meet.
Do this by placing the cursor before the first or after the last
bracket of an expression and pressing the ENTER key. Study the
output in the Listener window.
* All SCOM's functions are created from Lisp primitives *
What is a primitive? - it's a function built into the system.
Example: (+)
(+ 3 7)
(+ 3 7 5 6)
Let's look at situations where we might use arithmetic functions:
(+ 7 3)
(- 7 3)
(* 7 3)
(/ 7 3)
; a list of note lengths to be added together to make a zone
(setq rhy '(24 24 48 48 96 48))
(setq zone (apply '+ rhy))
(setq zone1 (+ 24 24 48 48 96 48))
(setq zone2 (* zone 4))
(setq zone3 (/ (* zone 4) zone))
; a practical example
(setq len '(96 48 48))
(setq mat '(a c b g r t a s = d e))
(setq pat '(a b c a))
(setq zones (symbol-repeat (length pat)
(list (/ (* (apply '+ len)
(length mat)) (length pat))))
)
A breakdown of this expression:
A core note-length value or the sum of a list of values (len) is
multiplied by the length of the symbol output (mat); then divided
by the length of the tonality symbol template (pat). This value is
then repeated the number of times corresponding to the length of
the tonality symbol list.
Note the primitives: length, apply, list
(length mat)
(apply '+ len)
(list (/ (* (apply '+ len) (length mat))(length pat))
;_________________________________________________________________________
; functions
(defun dec (x)
(1- x))
(dec 5)
(defun inc (x)
(1+ x))
(inc 5)
; shows operation of 1+ and 1- : equivalent to (- x 1) (+ x 1)
; it also shows how to make a function using the primitive defun.
Let's square some rhythmic values:
(defun sq (x)
(* x x))
(sq 5)
; this might be used to create zones from note-lengths
(defun square-values (list-of-values)
(mapcar 'sq list-of-values))
(square-values rhy)
; the primitive mapcar maps sq onto each value in the rhy list.
Here's another simple function using primitives mapcar and random:
(defun randomize-values (list-of-values)
(mapcar 'random list-of-values))
(setq newzones (randomize-values zones))
; and another, this time using an SCOM function integer-to-symbol:
(defun integers-to-symbols (list-of-values)
(mapcar 'integer-to-symbol list-of-values))
(setq mat2 (integers-to-symbols '(0 2 5 3 2 6 4)))
;______________________________________________________________
; list processing
(setq chords '(adf deba gib cdhi))
; try out these expressions sequentially
(car chords)
(first chords)
(cdr chords)
(cadr chords)
(second chords)
(caddr chords)
(third chords)
(cddr chords)
(cdddr chords)
(fourth chords)
(but-last chords)
(last chords)
; Lisp primitives car, cdr and associatives / derivatives
(setq progression
(list
(car chords)
(cdr chords)
(cadr chords)
(cddr chords)
(cdddr chords)
(but-last chords)
(last chords)))
; this shows how the primitive list is used
(defun select-one (pattern)
(nth (random (length pattern)) pattern))
(select-one '(1 2 3 4 5))
; a function to select one item from a list
; note the primitives: nth, random, length.
(nth 3 '(1 2 3 4))
(random 34)
(length '(1 2 3 4))
(select-one progression) ; evaluate this several times
(setq template '(a = a = = a = a))
(setq seq1 (fill-rest template (select-one progression))
seq2 (fill-rest template (select-one progression))
seq3 (fill-rest template (select-one progression))
seq4 (fill-rest template (select-one progression))
)
(setq phrase (append seq1 seq2 seq3 seq4))
; the primitive append joins all the sequences together
(setq link-material (fill-template template (fourth chords)))
(setq phrase+link (cons phrase (list link-material)))
Use cons and list to create nested expressions in the zone
support function
; ________________________________________________________________
; predicates
(cond ((and seq1 seq2) (fourth chords))
((and seq2 seq3) (third chords))
((and seq3 seq4) (second chords)))
; cond evaluates each predicate in turn until it finds one that is 'true'
; The neural expert makes extensive use of predicates
; this example converts symbols to note lengths
(setq mat1 (gen-random 0.5 32 '(a c d a g l c c g = n m g m e d c)))
(def-neuron rhyv
(in 1 'a) '192
(in 1 'g) '96
(in 1 'm) '48
(otherwise '24))
(setq rhyb (run-neuron 'rhyv mat1))
; to make our own symbol to length converter just with predicates
(defun symbol-to-length (symbol)
(cond ((equal symbol 'a) '(192))
((equal symbol 'g) '(96))
((equal symbol 'm) '(48))
(t '(24))))
(symbol-to-length 'a)
; but this doesn't cope with reading a list of symbols
; here are two solutions
(setq rhyy (flatten (mapcar 'symbol-to-length mat1)))
(defun make-length (pattern)
(prog (out)
loop
(cond ((null pattern) (return out)))
(setq out
(append out
(symbol-to-length
(car pattern))))
(setq pattern (cdr pattern))
(go loop)))
(setq rhyx (make-length mat1))
;________________________________________________________________________
; recursion
The function make-length shows recursion in action.
Here is a full example to explain what's happening.
The function retro is designed to reverse the order of a list.
(defun ; function
retro ; name
(pattern) ;list of arguments
(if (null pattern) nil ; a list of action the function is to take.
(append (last pattern)
(retro (butlast pattern)))))
(retro '(48 50 52))
1. The if function calls (null pattern) - is this list empty?
( - (null pattern) is a predicate!)
2. The list is'nt empty - so nil (the then or consequent) is NOT
returned
3. The else or alternative function append then takes the last of
the pattern and outputs this value (52)
4. Recursion now begins! retro's argument pattern is shortened by
butlast function to become (48 50).
5. This goes around again to if - it's still not an empty list so
on to append and the last of (48 50) which is (50). This joins (52)
as (52 50) - thanks to append which holds onto the values until the
first evaluation of if is true.
6. Recursion continues via butlast bringing it down to (48)
7. This goes around again to if - still a list, even though it's one
atom and on to append. The function last can only return (48). This
value is then appended to the growing list (52 50 48).
8. Finally, recursion finishes as the list disappears!
; theme
(defun retro (pattern)
(if (null pattern) nil
(append (last pattern)
(retro (butlast pattern)))))
; a variation!
(defun retro1 (pattern)
(cond
((null pattern) nil)
(t (append (retro1 (cdr pattern))
(list (car pattern))))))
(retro1 '(45 47 49))
; here are two extensions of the earlier select-one function
; the first avoids recursion.
(setq mat3 '(a b c d))
(defun select-two (pattern)
(list
(nth (random (length pattern)) pattern)
(nth (random (length pattern)) pattern)))
(select-two mat3)
; this is, in effect, similar to gen-random, and is recursive.
(defun select-many (n pattern)
(prog (out)
loop
(cond ((equal n (length out)) (return out)))
(setq out (append out (list (nth (random
(length pattern)) pattern))))
(go loop)))
(select-many 32 mat3)
; notice the predicate argument:
(cond ((equal n (length out)) (return out)))
This means - when the number chosen (n) becomes equal to the
number of recursions required to make the length of out the same
as n, out is returned.
Notice how the variable out is made.
(setq out (append out (list (nth etc. . .
- append collects together all the different results of looping the
expression until the predicate says 'stop!, you've reached the
same number of recursions as the number you set'.
- list has to be used because each result is a single atom - a or 1 -
and needs to be (a) or (1) to be collected and joined together
by append.
;__________________________________________________________________
Now, for some music. The following composition example uses many of
the functions and some of the material created in the tutorial.
The music is a piece for keyboard and percussion. Try compiling
the keyboard parts several times. You'll find that because of the
select-one function the progression changes each time.
Don't press the Eval Buffer button, just outline the whole example
and Eval Selection.
; composition example - 'chordal groove'
(setq tonal1 (activate-tonality (dorian c 5) (mixolydian a& 4)))
(setq tonal2 (activate-tonality (dorian c 3) (mixolydian a& 2)))
(setq chords '(adf deba gib cdhi))
(setq progression
(list
(car chords)
(cdr chords)
(cadr chords)
(cddr chords)
(cdddr chords)
(but-last chords)
(last chords)))
(setq template '(a = a = = a = a))
(setq seq1 (fill-rest template (select-one progression))
seq2 (fill-rest template (select-one progression))
seq3 (fill-rest template (select-one progression))
seq4 (fill-rest template (select-one progression)))
(setq phrase (append seq1 seq2 seq3 seq4))
(setq link-material (fill-template template (fourth chords)))
(setq phrase+link (append phrase link-material))
(setq chordal (append phrase phrase+link phrase))
(setq mat3 '(a b c d))
(setq mat4 (select-many 32 mat3))
(setq zonea (list (* (length phrase) 48)))
(setq zoneb (list (* (length phrase+link) 48)))
(setq zonec (append zonea zoneb zonea))
(def-instrument-symbol
pianoRH chordal
pianoLH (fill-template chordal mat4)
)
(def-instrument-length
default 1/8
)
(def-instrument-rhythm
cabasa '1/8 "---- ---" (z)
bassdr '1/4 "- - -" (b)
)
(def-instrument-velocity
default (select-many 32 '(112 84 74 64 74 54 54))
cabasa '(96 90 84 104 0 64 74 84)
bassdr '(110 0 0 0 84 0 0 96)
)
(def-instrument-zone
default zonec
)
(def-instrument-tonality
pianoRH tonal1
pianoLH tonal2
cabasa mt-32
bassdr mt-32
)
(compile-instrument "ccl;output:" "groove"
pianoRH
pianoLH
bassdr
cabasa
)